#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>



MODULE_DESCRIPTION("un_driver_en_mode_block");
MODULE_AUTHOR("mc,,,, (mc@MC-desktop)");
MODULE_LICENSE("$LICENSE$");



#define taille_secteur_noyau 512
#define MAJOR_NUM 120
#define Minors_val 16 /* nombre de partitions possibles */
#define N_SECTORS 2 /* nombre de secteurs utilisés par notre peripherique */
#define taille_secteur_peripherique 512 /* taille d'un secteur pour le peripherique */





/* representation du notre peripherique */

static struct peripherique_bloc_type {
        unsigned long size;
        spinlock_t lock;
        u8 *data;
        short users;
        struct gendisk *gd;
};

static struct peripherique_bloc_type *periph_bloc = NULL;
static struct request_queue *periph_bloc_file;






//***********************************************************************************
//***********************************************************************************
// cette fonction presente une copie simple de données entre notre peripherique
// et la memoire tampon
static void periph_bloc_transfer(struct peripherique_bloc_type *periph, unsigned long secteur,
                unsigned long nsect, char *buffer, int write)
{
    unsigned long debut = secteur*taille_secteur_peripherique;
    unsigned long taille = nsect*taille_secteur_peripherique;

    if ((debut + taille) > periph->size) { printk (KERN_NOTICE "taille depassee !\n");
        									return;
    										}


    if (write)  memcpy(periph->data + debut, buffer, taille);
    else  memcpy(buffer, periph->data + debut, taille);
}

//********************************************************************
//********************************************************************

static void periph_bloc_request(struct request_queue_t *file)
{
    struct request *req;


//La  fonction elv_next_request  permet d’obtenir la première requête
//non complétée dans la file, elle retourne NULL en cas où il n y pas
//encore de requêtes incomplètes. A savoir que la fonction elv_next_request
//n’enlève pas la requête de la file, elles sont enlevées lorsqu’elles sont terminées


    while ((req = elv_next_request(file)) != NULL) {


		//La fonction  block_fs_request nous informe sur le type de la requête,
		//si c’est une requête filesystem normale ou non. Dans notre cas on ne va
		//gérer que les requêtes de ce type. Donc on va mettre le paramètre « succeeded »
		//de la fonction « void end_request(struct request *req, int succeeded) » à 0 pour
		//dire qu’on n’a pas complété la requête avec succès et à 1 pour « avec succès »

        if (! blk_fs_request(req)) {
            printk (KERN_NOTICE "type de requete non pris en charge\n");
            end_request(req, 0);
            continue;
        }

        //rq_data_dir(struct request *req);
		//Cette fonction retourne 0 pour indiquer que c’est une requête de lecture
		//de données du périphérique , sinon elle indique que c’est une opération d’écriture

        periph_bloc_transfer(periph_bloc, req->sector, req->current_nr_sectors,
                        req->buffer, rq_data_dir(req));

        end_request(req, 1);



    }

}

//********************************************************************************
//********************************************************************************

// PARTIE OPTIONNELLE !!

//Malgré que notre périphérique est virtuel et n’a pas de relation avec les cylindres,
//et malgré que le noyau voit notre périphérique comme un tableau linéaire de secteurs ,
//on a intérêt à implémenter cette méthode car il existe encore quelques applications
//qui exigent ce type de données (la géométrie du disque) et ne peuvent pas fonctionner
//correctement sans ces informations

int periph_bloc_ioctl (struct inode *inode, struct file *filp,
                unsigned int cmd, unsigned long arg)
{
        long size;
        struct hd_geometry geo;

        switch(cmd) {
            case HDIO_GETGEO:
                size = periph_bloc->size*(taille_secteur_peripherique/taille_secteur_noyau);
                geo.cylinders = (size & ~0x3f) >> 6;
                geo.heads = 4;
                geo.sectors = 16;
                geo.start = 4;
                if (copy_to_user((void *) arg, &geo, sizeof(geo)))
                        return -EFAULT;
                return 0;
    }

    return -ENOTTY;



}


//***************************************************************************
//***************************************************************************

//  rendre les operations disponibles au système
// c'est le point d'entree du driver
static struct block_device_operations periph_bloc_ops = {
    .owner          = THIS_MODULE,
    .ioctl          = periph_bloc_ioctl

};


//***************************************************
//***************************************************
/** initialisation du module */

static int __init periph_bloc_init(void)
{
    static int ret;
	int cp;

		// enregistrement : obtention d'un major number
		//					+ creer une nvlle entree dans /proc/devices
        ret = register_blkdev(MAJOR_NUM, "periph_bloc");
        if (ret < 0) { printk(KERN_WARNING "erreur au niveau du major number \n");
                       return -EBUSY;
        			 }


		// allocation memoire
        periph_bloc = kmalloc(sizeof(struct peripherique_bloc_type), GFP_KERNEL);

        if (periph_bloc == NULL) {
                printk(KERN_WARNING "erreur au niveau de l'allocation memoire pour le peripherique\n");
                goto out_unregister;
        }



        memset(periph_bloc, 0, sizeof(struct peripherique_bloc_type));
        periph_bloc->size= N_SECTORS*taille_secteur_peripherique;
        periph_bloc->data = vmalloc(periph_bloc->size);

        if (periph_bloc->data == NULL) {
                printk(KERN_WARNING "erreur au niveau de l'allocation memoire pour les donnees\n");
                kfree(periph_bloc);
                goto out_unregister;
        }

        // initialisation du champ DATA à vide
	    for ( cp = 0 ; cp<  periph_bloc->size;cp++)
	    memcpy( periph_bloc->data + cp ,"" ,1);


        spin_lock_init(&periph_bloc->lock);

        periph_bloc_file = blk_init_queue(periph_bloc_request, &periph_bloc->lock);
        if (periph_bloc_file == NULL) {
                printk(KERN_WARNING "erreur au niveau de  blk_init_queue\n");
                goto out_free;
        }

        blk_queue_hardsect_size(periph_bloc_file, taille_secteur_peripherique);




    //Structure Gendisk : c'est la representation noyau d’un peripherique de type disque
	//Minors_val : nombre maximal de partitions (Minors_val=16 equivalent à 15 partitions)
      periph_bloc->gd = alloc_disk(Minors_val);
        if (!periph_bloc->gd) {
                printk(KERN_WARNING "erreur au niveau de alloc_disk pour gendisk\n");
                goto out_free;
        }

        periph_bloc->gd->major = MAJOR_NUM;
        periph_bloc->gd->first_minor = 0;
        periph_bloc->gd->fops = &periph_bloc_ops;
        periph_bloc->gd->private_data = periph_bloc;
        snprintf(periph_bloc->gd->disk_name, 10, "&percnt;s", "periph_bloc0");

        //la capacité en terme de secteurs  de 512 octets
        //meme si la taille des secteurs hardware est differente
        set_capacity(periph_bloc->gd, N_SECTORS);
        periph_bloc->gd->queue = periph_bloc_file;


        // l’allocation de la structure gendisk ne rend pas le disque disponible
        // pour le faire il faut utiliser cette commande
        add_disk(periph_bloc->gd);


        return 0;

out_free:
        vfree(periph_bloc->data);
        kfree(periph_bloc);
out_unregister:
        unregister_blkdev(MAJOR_NUM, "periph_bloc");

        return -ENOMEM;
}


//****************************************************************************
//****************************************************************************

static void __exit periph_bloc_exit(void)
{


		//suppression des informations de partitionnement
		//et de la structure gendisk passée au système
        del_gendisk(periph_bloc->gd);


        //suppression de la reference crée au debut vers
        //la structure gendisk (operation contraire de alloc_disk())
        put_disk(periph_bloc->gd);

        unregister_blkdev(MAJOR_NUM, "periph_bloc");
        blk_cleanup_queue(periph_bloc_file);

        vfree(periph_bloc->data);
        kfree(periph_bloc);


}

module_init(periph_bloc_init);
module_exit(periph_bloc_exit);

